home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 25 / Cream of the Crop 25.iso / doom / axxwar_1.zip / SOURCES / CBOTAI.QC < prev    next >
Text File  |  1997-03-05  |  50KB  |  1,718 lines

  1. // AxxWars v0.8
  2.  
  3. /*
  4.  
  5. CBOT AI ENGINE
  6.  
  7. Copyright Cameron Newham, 1996
  8.  
  9. Motto: "All other bots must die!"
  10.  
  11. */
  12.  
  13. void()  cbt_shot1;
  14. void()  cbt_nail1;
  15. void()  cbt_light1;
  16. void()  cbt_rocket1;
  17. void()  cbt_blaze1;
  18. void()  W_FireShotgun;
  19. void()  W_FireSuperShotgun;
  20. void()  W_FireGrenade;
  21. void()  W_FireRocket;
  22. void()  WaterMove;
  23. void()  CheckRules;
  24. void()  CheckPowerups;
  25.  
  26.  
  27. // cn_wpt spawn function
  28. void () cn_wpt =
  29. {
  30.   if (!self.speed)
  31.     self.speed = 0;
  32.   if (!self.height)
  33.     self.height = 1;
  34.   if (self.angles == '0 0 0')
  35.     self.angles = '0 360 0';
  36.   if (!self.button0)
  37.     self.button0 = 0;
  38.   SetMovedir();
  39.   if (!self.swim_flag)
  40.     self.swim_flag = FALSE;
  41. };
  42.  
  43.  
  44. // slide the cbot sideways
  45. void (float slide_amount) cbot_slide =
  46. {
  47.   local float ofs;
  48.  
  49.         if (self.lefty)
  50.           ofs = 90;
  51.         else
  52.           ofs = -90;
  53.         
  54.         if (walkmove (self.ideal_yaw + ofs, (random() * slide_amount) + 12))
  55.           return;
  56.                 
  57.         self.lefty = 1 - self.lefty;
  58.         
  59.         walkmove (self.ideal_yaw - ofs, (random() * slide_amount) + 12);
  60. };
  61.  
  62.  
  63. /*-----------------------------------------------------------------
  64.   CBOT Fighting Functions
  65. -----------------------------------------------------------------*/
  66.  
  67.  
  68. /*
  69.   Detect incoming missile - return true if there
  70.   is a missile within 270 units (within 0.27 seconds of impact)
  71.   Also works for grenades on ground.
  72. */
  73. float () cbot_incoming_missile =
  74. {
  75.   local entity article;
  76.  
  77.   article = findradius(self.origin, 270);
  78.  
  79.   while (article)
  80.   {
  81.     if ((article.classname == "rocket") ||
  82.         (article.classname == "grenade"))
  83.     {
  84.       if (article.owner != self)
  85.       {
  86.         return TRUE;
  87.       }
  88.     }
  89.     article = article.chain;
  90.   }
  91.  
  92.   return FALSE;
  93. };
  94.  
  95.  
  96. /*----------------------------------------------
  97. The player is in view, so decide to move or launch an attack
  98. Returns FALSE if movement should continue
  99. ----------------------------------------------*/
  100. float() cbot_check_attack =
  101. {
  102.   local vector    spot1, spot2;   
  103.   local entity    targ;
  104.   local float     chance;
  105.   local float     r;      
  106.  
  107.   targ = self.enemy;
  108.  
  109.   // see if any entities are in the way of the shot
  110.   spot1 = self.origin + self.view_ofs;
  111.   spot2 = targ.origin + targ.view_ofs;
  112.  
  113.   traceline (spot1, spot2, FALSE, self);
  114.         
  115.         
  116.   if (trace_ent != targ)
  117.     return FALSE;           // don't have a clear shot
  118.                         
  119.   if (trace_inopen && trace_inwater)
  120.     return FALSE;                   // sight line crossed contents
  121.  
  122.                 
  123.   if (time < self.attack_finished)
  124.     return FALSE;
  125.                 
  126.   r = vlen (spot1 - spot2);
  127.         
  128.   if (r > 1600)
  129.     return FALSE;
  130.                 
  131.   if (r < 120)
  132.   {
  133.     chance = 0.97;
  134.   }
  135.   else if (r < 500)
  136.   {
  137.     chance = 0.87;
  138.   }
  139.   else if (r < 1300)
  140.   {
  141.     chance = 0.67;
  142.   }
  143.   else
  144.     chance = 0;
  145.  
  146.   if (random () < chance)
  147.     return TRUE;
  148.  
  149.   return FALSE;
  150. };
  151.  
  152.  
  153.  
  154. /*----------------------------------------------
  155. Determine if we are facing the target +/- 0.5 Deg
  156. in yaw, +/- 1 Deg pitch
  157. ----------------------------------------------*/
  158. float() cbot_facing_ideal =
  159. {
  160.         local   float   delta_yaw;
  161.         local   float   delta_pitch;
  162.         
  163.         delta_yaw = anglemod(self.angles_y - self.ideal_yaw);
  164.         delta_pitch = fabs(self.ideal_pitch - self.angles_x);
  165.  
  166.         if ((delta_yaw > 0.5 && delta_yaw < 359.5) || (delta_pitch > 1)) 
  167.                 return FALSE;
  168.         return TRUE;
  169. };
  170.  
  171.  
  172. void() cbot_set_ammo =
  173. {
  174.         self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS) );
  175.         
  176.         if (self.weapon == IT_NONE)
  177.         {
  178.                 self.currentammo = 0;
  179.         }
  180.         else if (self.weapon == IT_SHOTGUN)
  181.         {
  182.                 self.currentammo = self.ammo_shells;
  183.                 self.items = self.items | IT_SHELLS;
  184.         }
  185.         else if (self.weapon == IT_SUPER_SHOTGUN)
  186.         {
  187.                 self.currentammo = self.ammo_shells;
  188.                 self.items = self.items | IT_SHELLS;
  189.         }
  190.         else if (self.weapon == IT_NAILGUN)
  191.         {
  192.                 self.currentammo = self.ammo_nails;
  193.                 self.items = self.items | IT_NAILS;
  194.         }
  195. // AXXBL START
  196.        else if (self.weapon == IT_BLAZEGUN)
  197.         {
  198.                 self.currentammo = self.ammo_nails;
  199.                 self.items = self.items | IT_NAILS;
  200.         }
  201. // AXXBL END
  202.        else if (self.weapon == IT_SUPER_NAILGUN)
  203.         {
  204.                 self.currentammo = self.ammo_nails;
  205.                 self.items = self.items | IT_NAILS;
  206.         }
  207.         else if (self.weapon == IT_GRENADE_LAUNCHER)
  208.         {
  209.                 self.currentammo = self.ammo_rockets;
  210.                 self.items = self.items | IT_ROCKETS;
  211.         }
  212.         else if (self.weapon == IT_ROCKET_LAUNCHER)
  213.         {
  214.                 self.currentammo = self.ammo_rockets;
  215.                 self.items = self.items | IT_ROCKETS;
  216.         }
  217.         else if (self.weapon == IT_LIGHTNING)
  218.         {
  219.                 self.currentammo = self.ammo_cells;
  220.                 self.items = self.items | IT_CELLS;
  221.         }
  222.         else
  223.         {
  224.                 self.currentammo = 0;
  225.         }
  226. };
  227.  
  228.  
  229. /*
  230. Determine the best weapon to use against the current enemy.
  231. This incorporates corrections for near R/L use and grenade, etc.
  232. */
  233. float() cbot_best_weapon_now =
  234. {
  235.         local   float   it;
  236.         local   entity  targ;
  237.         local   float   targ_dist;
  238.         local   vector  targ_pitch;
  239.         local vector    spot1, spot2;   
  240.  
  241.   targ = self.enemy;
  242.  
  243.   spot1 = self.origin; // + self.view_ofs;
  244.   spot2 = targ.origin; // + targ.view_ofs;  // may not be defnd for cbot! check this
  245.  
  246.   targ_dist = vlen (spot1 - spot2);
  247.         
  248.         it = self.items;
  249.  
  250.         if((self.ammo_cells >= 1) && (it & IT_LIGHTNING) && (targ_dist < 950))
  251.                 return IT_LIGHTNING;
  252.         else if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) &&
  253.                ((targ_dist > 150) || (self.health >= 90)))
  254.                 return IT_ROCKET_LAUNCHER;
  255.         else if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) &&
  256.                ((targ_dist < 500) || (self.ideal_pitch < 0)) &&
  257.                ((targ_dist > 120) || (self.health >= 90)))
  258.                 return IT_GRENADE_LAUNCHER;
  259.         else if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) &&
  260.                (targ_dist < 1000))
  261.                 return IT_SUPER_NAILGUN;
  262.       // AXXBL START
  263.       if((self.ammo_nails >= 2) && (it & IT_BLAZEGUN) && (targ_dist < 950))
  264.                 return IT_BLAZEGUN;
  265.       // AXXBL END
  266.             else if(self.ammo_nails >= 1 && (it & IT_NAILGUN) )
  267.                 return IT_NAILGUN;
  268.         else if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) )
  269.                 return IT_SUPER_SHOTGUN;
  270.         else if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) )
  271.                 return IT_SHOTGUN;
  272.                 
  273.         return IT_NONE;
  274. };
  275.  
  276.  
  277. /*
  278. Determine best weapon on picking up backpacks/ammo
  279. */
  280. float() cbot_best_weapon =
  281. {
  282.         local   float   it;
  283.         
  284.         it = self.items;
  285.  
  286. // AXXBL START     else 
  287. if(self.ammo_nails >= 2 && (it & IT_BLAZEGUN) )
  288.                 return IT_BLAZEGUN;
  289. // AXXBL END
  290.         if(self.ammo_cells >= 1 && (it & IT_LIGHTNING) )
  291.                 return IT_LIGHTNING;
  292.         else if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) )
  293.                 return IT_ROCKET_LAUNCHER;
  294.         else if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) )
  295.                 return IT_GRENADE_LAUNCHER;
  296.         else if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) )
  297.                 return IT_SUPER_NAILGUN;
  298.         else if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) )
  299.                 return IT_SUPER_SHOTGUN;
  300.         else if(self.ammo_nails >= 1 && (it & IT_NAILGUN) )
  301.                 return IT_NAILGUN;
  302.         else if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) )
  303.                 return IT_SHOTGUN;
  304.                 
  305.         return IT_NONE;
  306. };
  307.  
  308. /*---------------------------------------------
  309. check if we have ammo or not and change weapons
  310. if we are out.
  311. ---------------------------------------------*/
  312. float() cbot_check_no_ammo =
  313. {
  314.         if (self.currentammo > 0)
  315.                 return TRUE;
  316.  
  317.         if (self.weapon == IT_NONE)
  318.                 return TRUE;
  319.         
  320.         self.weapon = cbot_best_weapon_now ();
  321.  
  322.         cbot_set_ammo ();
  323.         
  324.         // drop the weapon down
  325.         return FALSE;
  326. };
  327.  
  328.  
  329. /*---------------------------------------------
  330. Called from combat.qc for attacks on us
  331.  
  332. Determines what action to take:
  333. ATTACK - attack outright
  334. OTFATTACK - continue search but fire at enemy
  335. WITHDRAW - try and hide
  336.  
  337. If inflictor != world then we have been hit
  338. by a missile - we can use this info to determine
  339. enemy strengths.
  340. ---------------------------------------------*/
  341. float (entity e_target, entity e_inflictor) cbot_determine_mode =
  342. {
  343.   if ((self.botmode == CAMPER) ||
  344.       (self.items & IT_INVISIBILITY) ||
  345.       (self.items & IT_INVULNERABILITY) ||
  346.       (self.items & IT_QUAD))
  347.     return ATTACK;  // de rigor for a camper or specials
  348.   if (self.weapon == IT_NONE)
  349.     return WITHDRAW;  // de rigor for empty handed combatent
  350.   if ((self.armorvalue > 95) ||
  351.       (self.health <= 7))
  352.     return ATTACK;  // attack with whatever we have if great armour
  353.                     // or we are nearly dead
  354.   if ((self.armorvalue > 50) &&
  355.       ((self.pi9 == 1) ||
  356.        (self.pi8 == 1) ||
  357.        (self.pi7 == 1) ||
  358.        (self.pi6 == 1) ||
  359.        (self.pi5 == 1) ||
  360.        (self.pi4 == 1)))
  361.     return ATTACK;  // if good armour and weapons > sg then do it
  362.   if ((self.armorvalue > 20) &&
  363.       ((self.pi8 == 1) ||
  364.        (self.pi7 == 1) ||
  365.        (self.pi6 == 1) ||
  366.        (self.pi5 == 1) ||
  367.        (self.pi4 == 1)))
  368.     return ATTACK;  // if poor armour, good weapons
  369.                     // then do it
  370.  
  371.   if ((self.botmode == SEARCH) || 
  372.       (self.botmode == OTFATTACK))
  373.   {
  374.     // no armour so be careful
  375.     if (e_inflictor)
  376.     {
  377.       if (e_inflictor.classname != "rocket")
  378.         return ATTACK;  // no armour and attack wasn't a rocket and
  379.                         // WE were attacked
  380.     }
  381.     else
  382.     if ((self.health > (e_target.health * 1.2)) ||
  383.         (random() > 0.95))
  384.       return ATTACK;  // we are attacking someone - do it if our health
  385.                       // is 1.2 x better than theirs OR random choice
  386.  
  387.   }
  388.  
  389.   if ((self.botmode == SEARCH) || 
  390.       (self.botmode == OTFATTACK))
  391.     return OTFATTACK;  // if we are searching and have any weapon
  392.                        // then use it while searching
  393.   
  394.  
  395.   return WITHDRAW; // we are in trouble so hide!
  396. };
  397.  
  398.  
  399. /*---------------------------------------------
  400. Fire the current weapon
  401. ---------------------------------------------*/
  402. void() cbot_fire_weapon =
  403. {
  404.  
  405.   if (!cbot_check_no_ammo ())
  406.     return;
  407.  
  408. //  if (cbot_friendly == 1)    // Debug: Don't shoot at all. 
  409. //    return;
  410.  
  411.   self.v_angle = self.angles;
  412.  
  413.   self.show_hostile = time + 1;   // wake monsters up
  414.  
  415.   if (self.weapon == IT_NONE)
  416.     return;  // should never happen
  417.  
  418.   else if (self.weapon == IT_SHOTGUN)
  419.   {
  420.     self.v_angle_x = -1 * self.v_angle_x;
  421.     cbt_shot1 ();
  422.     makevectors (self.v_angle);
  423.     W_FireShotgun ();
  424.     self.attack_finished = time + 0.5;
  425.   }
  426.   else if (self.weapon == IT_SUPER_SHOTGUN)
  427.   {
  428.     self.v_angle_x = -1 * self.v_angle_x;
  429.     cbt_shot1 ();
  430.     makevectors (self.v_angle);
  431.     W_FireSuperShotgun ();
  432.     self.attack_finished = time + 0.7;
  433.   }
  434.   else if (self.weapon == IT_NAILGUN)
  435.   {
  436.     self.blasttime = time + 2.3 + 2.1 * random();
  437.     self.think = cbt_nail1;  //nails & lightning as thinks! others as subs
  438.     self.nextthink = time + 0.1;
  439.   }
  440. // AXXBL START
  441.     else if (self.weapon == IT_BLAZEGUN)
  442.           {
  443.             self.v_angle_x = self.v_angle_x * -1;
  444.         cbt_blaze1;  
  445.             self.nextthink = time + 0.1;
  446.           }
  447. // AXXBL END
  448.  
  449.   else if (self.weapon == IT_SUPER_NAILGUN)
  450.   {
  451.     self.blasttime = time + 1.8 + 2 * random();
  452.     self.think = cbt_nail1;
  453.     self.nextthink = time + 0.1;
  454.   }
  455.   else if (self.weapon == IT_GRENADE_LAUNCHER)
  456.   {
  457.     self.v_angle_x = self.v_angle_x * -1;
  458.     cbt_rocket1();
  459.     W_FireGrenade();
  460.     self.attack_finished = time + 0.6;
  461.   }
  462.   else if (self.weapon == IT_ROCKET_LAUNCHER)
  463.   {
  464.     self.v_angle_x = self.v_angle_x * -1;
  465.     cbt_rocket1();
  466.     W_FireRocket();
  467.     self.attack_finished = time + 0.8;
  468.   }
  469.   else if (self.weapon == IT_LIGHTNING)
  470.   {
  471.     self.blasttime = time + 1.5 + 2 * random();
  472.     self.think = cbt_light1;
  473.     self.nextthink = time + 0.1;
  474. //    self.attack_finished = time + 0.1;
  475.     sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM);
  476.   }
  477. };
  478.  
  479.  
  480. /*----------------------------------------------------
  481. Turns to face the target - includes a correction
  482. for the enemy's velocity
  483. ----------------------------------------------------*/
  484. void() cbot_track_target =
  485. {
  486.   local vector los;
  487.   local vector los_angles;
  488.   local float  o_range;
  489.   local float  missile_factor;
  490.   local vector spotty_squid;  // adds a tiny random for stationary targets
  491.  
  492. local string st;
  493.  
  494.   o_range = vlen(self.enemy.origin - self.origin);
  495.  
  496.   if ((self.weapon == IT_ROCKET_LAUNCHER) ||
  497.       (self.weapon == IT_NAILGUN) ||
  498.       (self.weapon == IT_SUPER_NAILGUN) 
  499.     || (self.weapon == IT_BLAZEGUN)     // AXXBL
  500.     )
  501.     missile_factor = o_range / (1900 + random() * 700);  // add scatter
  502.   else if (self.weapon == IT_GRENADE_LAUNCHER)
  503.     missile_factor = o_range / (1000 + random() * 50);  // add scatter
  504.   else
  505.     missile_factor = 0;  //other weapons are instantaneous
  506.  
  507.   // apply a small randomiser of the distance is large
  508.   if (o_range > 300)
  509.     spotty_squid = ('9 9 8') * (random() - 0.5);
  510.   else
  511.     spotty_squid = '0 0 0';
  512.  
  513.     // Face cbot towards the specified angles
  514.     los = self.enemy.origin - self.origin + self.enemy.velocity*missile_factor
  515.           + spotty_squid;
  516.  
  517.     los_angles = vectoangles (los);
  518.     self.ideal_yaw = los_angles_y;
  519.     self.ideal_pitch = los_angles_x;
  520.     if (self.ideal_pitch > 180)
  521.       self.ideal_pitch = self.ideal_pitch - 360;  //stupid id mistake
  522.         
  523.     ChangeYaw();
  524.     ChangePitch();
  525. };
  526.  
  527.  
  528. /*---------------------------------------------
  529. Launch an attack at the self.enemy
  530. ---------------------------------------------*/
  531. void() cbot_attack =
  532. {
  533.  
  534.   if (cbot_incoming_missile())
  535.     cbot_slide(12);
  536.  
  537.   if (visible (self.enemy))
  538.   {
  539.     cbot_track_target();
  540.     self.sighted_time = time + 1 + 2 * random(); //wait random 2-3 seconds
  541.  
  542.     if (cbot_check_attack() ||
  543.         (self.botmode == OTFATTACK))
  544.     {
  545.       // only launch an attack if we feel like it
  546.       // or if we are attacking on-the-fly (once only)
  547.       // and pointing in the right direction.
  548.       if (cbot_facing_ideal())
  549. //      if (infront(self.enemy))
  550.       if (time > self.attack_finished)
  551.       {
  552.         self.weapon = cbot_best_weapon_now (); // need to do this to set best weapon for job
  553.         cbot_fire_weapon();
  554.       }
  555.     }
  556.     else
  557.     {
  558. //bprint ("readjust position instead of fire\n");
  559.         
  560.       cbot_track_target();
  561.  
  562.       if (self.botmode != JUMP)
  563.       {
  564.         // only do this if not in mid-air
  565.         cbot_slide(14);
  566.       }
  567.     }
  568.   }
  569.   else
  570.   {
  571.     // Enemy is not visible (grenade attack, hiding or delayed die)
  572.     // so move
  573.     if ((self.sighted_time < time) ||
  574.         (self.enemy.health <= 0))
  575.     {
  576.       // we waited for a time but the enemy has gone.  go back
  577.       // to normal duties.
  578.       self.botmode = SEARCH;
  579.       self.enemy = world;
  580.       self.goal_status = 0;
  581.     }
  582.   }
  583.  
  584.   if (cbot_incoming_missile())
  585.     cbot_slide(8);
  586.  
  587. };
  588.  
  589.  
  590.  
  591. void() cbot_attack_initial =
  592. {
  593.   cbot_attack();
  594. };
  595.  
  596.  
  597.  
  598. /*-----------------------------------------------------------------
  599.   Move and General Functions
  600. -----------------------------------------------------------------*/
  601.  
  602.  
  603.  
  604. void() cbot_jump =
  605. {
  606.   local vector jump_vec;
  607.   local float  vel;
  608.  
  609.         // set XY even if not on ground, so the jump will clear lips
  610.         vel = 240 + random() * 25;
  611.         makevectors (self.angles);  // calculate pointing vectors
  612.  
  613.         self.velocity_x = v_forward_x * vel;
  614.         self.velocity_y = v_forward_y * vel;
  615.         
  616.         if ( (self.flags & FL_ONGROUND) )
  617.         {
  618.           self.flags = self.flags - FL_ONGROUND;
  619.           self.velocity_z = vel;
  620.           self.botmode = JUMP;
  621.         }
  622.  
  623.         // allow some time for the jumping and the next check
  624.         // as we may be jumping into a teleporter and we want to
  625.         // find the next waypoint on the other side.
  626.         self.nextthink = time + 0.3;
  627.  
  628.         sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
  629. };
  630.  
  631.  
  632. void() cbot_watchdog =
  633. {
  634.   local float  dist;
  635.   local vector test_vectors;
  636.   local vector top;
  637.  
  638.   // check to see if the bot has moved or is stuck
  639.   dist = vlen (self.origin - self.old_posn);
  640.  
  641.   if (dist < 25)
  642.   {
  643.     // we are probably stuck
  644.     makevectors (self.angles);  // calculate pointing vectors
  645.     test_vectors = self.origin + (self.maxs_x + 16) * v_forward;
  646.     top = self.origin;
  647.     test_vectors_z = test_vectors_z + self.maxs_z;
  648.     top_z = top_z + self.maxs_z;
  649.     traceline (top, test_vectors, FALSE, self);
  650.     if (trace_fraction < 1.0)
  651.     {
  652.       // we have hit a wall
  653.       self.goalentity = world;  //nullify any previous goal
  654.       self.goal_status = 0;
  655.       self.think = self.th_stand;  // go and reconfigure
  656.     }
  657.     else
  658.     {
  659.       // could be a horizontal grill or a gap
  660.       top = self.origin + ((self.maxs_x + 16) * trace_fraction) * v_forward;
  661.       test_vectors = top;
  662.       test_vectors_z = test_vectors_z - 256; // drop a plumb well down
  663.       traceline (top, test_vectors, TRUE, self);
  664.       if ((256 * trace_fraction) < (self.size_z - 8))
  665.       {
  666.         // must be a grill or a low obstacle
  667.         self.goalentity = world;  //nullify any previous goal
  668.         self.goal_status = 0;
  669.         self.think = self.th_stand;  // go and reconfigure
  670.       } else
  671.       {
  672.         cbot_jump();
  673.       }
  674. /*
  675.  
  676. // clever code that doesn't seem to work...
  677. // probably needs tweaking to make it work
  678.  
  679.       if ((256 * trace_fraction) > (self.size_z + 4))
  680.       {
  681.         // must be next to a surface discontinuity, so jump
  682. bprint ("hit abyss\n");
  683.         cbot_jump();
  684.       } else
  685.       {
  686.         // may be over a surface discontinuity
  687.         test_vectors = self.origin;
  688.         test_vectors_z = test_vectors_z - 256; // drop plumb line
  689.         traceline (self.origin, test_vectors, TRUE, self);
  690.         if ((256 * trace_fraction) > (self.size_z + 4))
  691.         {
  692.           // we are over a surface discontinuity (gap)
  693. bprint ("over gap\n");
  694.           cbot_jump();
  695.         }
  696.         else
  697.         {
  698.           // we are stuck (reasons unknown)
  699.           bprint ("C-Bot ");
  700.           bprint (self.targetname);
  701.           bprint (" is stuck\n");
  702.         }
  703.       }
  704. */
  705.  
  706.     }
  707.   }
  708.  
  709.   self.old_posn = self.origin;  //update the old position
  710. };
  711.  
  712.  
  713.  
  714.  
  715. float (float itype) cbot_got_item =
  716. {
  717.   if (itype == 23)
  718.     return self.pi1;
  719.   if (itype == 22)
  720.     return self.pi2;
  721.   if (itype == 21)
  722.     return self.pi3;
  723.   if (itype == 16)
  724.     return self.pi4;
  725.   if (itype == 15)
  726.     return self.pi5;
  727.   if (itype == 14)
  728.     return self.pi6;
  729.   if (itype == 13)
  730.     return self.pi7;
  731.   if (itype == 12)
  732.     return self.pi8;
  733.   if (itype == 11)
  734.     return self.pi9;
  735. };
  736.  
  737.  
  738. void() cbot_prioritise =
  739. {
  740.   local   float   it;
  741.         
  742.   it = self.items;
  743.  
  744.   // this code here is a hack so that weapons gathered from
  745.   // a backpack will show up on list - here just to sync everything
  746.   // (perhaps it should be in backpack section of items.qc - oh well)
  747.   // Addendum: this code also deals with lack of ammo - if the weapon
  748.   // has run out then it is considered useless (not found)
  749.  
  750.         if(self.ammo_cells >= 1 && (it & IT_LIGHTNING) )
  751.           self.pi5 = 1;
  752.         else
  753.           self.pi5 = 0;
  754.  
  755.         if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) )
  756.           self.pi4 = 1;
  757.         else
  758.           self.pi4 = 0;
  759.  
  760.         if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) )
  761.           self.pi6 = 1;
  762.         else
  763.           self.pi6 = 0;
  764.  
  765.         if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) )
  766.           self.pi7 = 1;
  767.         else
  768.           self.pi7 = 0;
  769.  
  770.         if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) )
  771.           self.pi9 = 1;
  772.         else
  773.           self.pi9 = 0;
  774.  
  775.         if(self.ammo_nails >= 1 && (it & IT_NAILGUN) )
  776.           self.pi8 = 1;
  777.         else
  778.           self.pi8 = 0;
  779.  
  780.   
  781.   // armour damage changes our priorities!
  782.   if (self.armorvalue < 150)
  783.     self.pi1 = 0;
  784.   if (self.armorvalue < 100)
  785.     self.pi2 = 0;
  786.   if (self.armorvalue < 50)
  787.     self.pi3 = 0;
  788.  
  789. /*  // get low on rockets - so re-prioritise to important
  790.   if (self.ammo_rockets < 2)
  791.   {
  792.     self.pi4 = 0;
  793.     self.pi6 = 0;
  794.   }
  795.   if (self.ammo_nails < 4)
  796.   {
  797.     self.pi7 = 0;
  798.     self.pi8 = 0;
  799.   }
  800. */
  801. };
  802.  
  803.  
  804. /*---------------------------------------------
  805. Find nearest enemy
  806. ---------------------------------------------*/
  807. entity () cbot_find_nearest_enemy =
  808. {
  809.   local entity article, best_article;
  810.   local float o_range;
  811.   local float best_dist;
  812.   local vector los, los_angles;
  813.  
  814.   best_article = world;
  815.   best_dist = 9999;
  816.  
  817. if (cbot_friendly != 1)
  818.     {
  819.   // find closest player
  820.   article = find(world, classname, "player");
  821.  
  822.   while (article != world)
  823.   {
  824.     o_range = vlen(article.origin - self.origin);
  825.     traceline (self.origin, article.origin, TRUE, self);
  826.  
  827.     if ((trace_fraction == 1.0) &&
  828.         (o_range <= best_dist) &&
  829.         (article.health > 0) &&
  830.         (article != self))
  831.     {
  832.       los = article.origin - self.origin; // determine if pitch too great
  833.       los_angles = vectoangles (los);
  834.       if (los_angles_x > 180)
  835.         los_angles_x = los_angles_x - 360;  //vectoangles returns 0 to 360
  836.                                             // NOT negative values
  837.       if (fabs(los_angles_x) < 54)
  838.       {
  839.         best_article = article;
  840.         best_dist = o_range;
  841.       }
  842.     }
  843.     //next
  844.     article = find(article, classname, "player");
  845.   } //end while
  846.   
  847. }    
  848.  
  849.   // find closest cbot
  850.   article = find(world, classname, "cbot");
  851.   while (article != world)
  852.   {
  853.     o_range = vlen(article.origin - self.origin);
  854.     traceline (self.origin, article.origin, TRUE, self);
  855.  
  856.     if ((trace_fraction == 1.0) &&
  857.         (o_range <= best_dist) &&
  858.         (article.health > 0) &&
  859.         (article != self))
  860.     {
  861.       los = article.origin - self.origin; // determine if pitch too great
  862.       los_angles = vectoangles (los);
  863.       
  864.       if ((los_angles_x < 54) || (los_angles_x > 306))
  865.       {
  866.         best_article = article;
  867.         best_dist = o_range;
  868.       }
  869.     }
  870.     //next
  871.     article = find(article, classname, "cbot");
  872.   } //end while
  873.  
  874.   return best_article;
  875. };
  876.  
  877.  
  878.  
  879. /*-------------------------------------------
  880. Get the nearest visible prioritised waypoint.
  881. findp: 0 = any paths  1 = items  2 = general path,
  882. ignore: 100 = none ignored  # = ignore item type
  883. endp: 0 = ignore endpoints  1 = find endpoints
  884. Returns world if none available.
  885. -------------------------------------------*/
  886. entity( float findp, float ignore, float endp ) cbot_get_waypoint =
  887. {
  888.   local float o_range;
  889.   local entity article;
  890.   local float best_dist;
  891.   local entity best_article;
  892.   local float best_items;
  893.   local vector los, los_angles;
  894.  
  895.       best_dist = 9999.0; // set our best range to a large value;
  896.       best_article = world;
  897.       best_items = 0;
  898.  
  899.       // find a waypoint and go to it
  900.       article = find(world, classname, "cn_wpt");
  901.       while (article != world)
  902.       {
  903.         // find best waypoint
  904.         if (((findp == 1) && (article.items > best_items)) ||
  905.             ((findp == 2) && (article.items == 0)) ||
  906.              (findp == 0))
  907.         {
  908.           o_range = vlen(article.origin - self.origin);
  909.           traceline (self.origin, article.origin, TRUE, self);
  910.  
  911.           if ((trace_fraction == 1.0) &&
  912.               ((o_range <= best_dist) ||                // less range
  913.               (article.items > best_items)) &&
  914.               (!cbot_got_item (article.items)) &&
  915.               (article != self.goalentity) &&
  916.               ((ignore == 100) || (article.items != ignore)) &&
  917.               ((article.target != "-") || endp))
  918.           {
  919.             // ignore anything that's blocked or bigger than best range
  920.             // (unless its higher priority) or if we have it
  921.             // or if we just came from there (end waypoints) or
  922.             // any end waypoint (which are useless, unless told to).
  923.  
  924.             // now ignore anything below our field of view
  925.  
  926.             los = article.origin - self.origin; // determine if pitch too great
  927.             los_angles = vectoangles (los);
  928.             if (los_angles_x > 180)
  929.             los_angles_x = los_angles_x - 360;  //vectoangles returns 0 to 360
  930.                                                 // NOT negative values
  931.             if (fabs(los_angles_x) < 54)
  932.             {
  933.               best_article = article;
  934.               best_dist = o_range;
  935.               best_items = article.items;
  936.             }
  937.           }
  938.         }
  939.         //next
  940.         article = find(article, classname, "cn_wpt");
  941.       } //end while
  942.  
  943.   return (best_article);
  944. };
  945.  
  946.  
  947.  
  948. /*---------------------------------------------
  949. creates a sub-goal.  used to target objects
  950. like packs, weapons, armour and health, etc.
  951. ---------------------------------------------*/
  952. void(entity article) cbot_create_subgoal =
  953. {
  954.         self.origin_save = spawn();      // save origin for backtracking
  955.         self.origin_save.think = SUB_Remove;
  956.         self.origin_save.nextthink = time + 120;  //remove after 2 mins
  957.  
  958.         // save the primary goal target (where we were going to
  959.         // when interrupted to do the sub-goal
  960.         if (self.goalentity != world)
  961.           self.oldgoalname = self.goalentity.targetname;
  962.         else
  963.           self.oldgoalname = "none";
  964.  
  965.         setorigin (self.origin_save, self.origin);
  966.         self.goal_status = 3;            // on a sub-goal now
  967.  
  968.         /* we need to spawn a sub-goal for the article because when
  969.            it gets touched it may disappear (cf: backpack) and the
  970.            origin will become invalid */
  971.  
  972.         self.goalentity = spawn();  // spawn a sub-goal
  973.         self.goalentity.think = SUB_Remove;
  974.         self.goalentity.nextthink = time + 140;
  975.         setorigin (self.goalentity, article.origin);
  976. };
  977.  
  978.  
  979. /*---------------------------------------------------
  980. Checks to see if the ammo is at maximum already.
  981. Broken out into separate procedure for readability.
  982. ---------------------------------------------------*/
  983. float (entity article) cbot_check_max_ammo =      
  984. {
  985.   if ((article.classname == "item_cells") &&
  986.       (self.ammo_cells == 100))
  987.     return FALSE;
  988.  
  989.   if ((article.classname == "item_rockets") &&
  990.       (self.ammo_rockets == 100))
  991.     return FALSE;
  992.  
  993.   if ((article.classname == "item_shells") &&
  994.       (self.ammo_shells == 100))
  995.     return FALSE;
  996.  
  997.   if ((article.classname == "item_spikes") &&
  998.       (self.ammo_nails == 200))
  999.     return FALSE;
  1000.  
  1001.   return TRUE;
  1002. };
  1003.  
  1004.  
  1005. /*---------------------------------------------------
  1006. Find on-the-fly items like health, ammo and specials.
  1007. These are prioritised automagically in this routine
  1008. to get what the bot really needs at the time.  Order is:
  1009.  
  1010. health (<50%)
  1011. specials
  1012. ammo
  1013.  
  1014. Returns "world" if none.
  1015. ---------------------------------------------------*/
  1016. entity () find_otf_items =
  1017. {
  1018.   local float o_range;
  1019.   local entity article;
  1020.   local float best_dist;
  1021.   local entity best_article;
  1022.  
  1023.   // find any nearby item
  1024.   article = findradius (self.origin, 200.0);
  1025.  
  1026.   best_dist = 9999.0; // set our best range to a large value;
  1027.   best_article = world;
  1028.  
  1029.   while (article)
  1030.   {
  1031.     o_range = vlen(article.origin - self.origin);
  1032.     // trace a line - raise target slightly for visibility on stairs, etc
  1033.     traceline (self.origin, article.origin + '0 0 10', TRUE, self);
  1034.     if (trace_fraction == 1.0)
  1035.     {
  1036.       // ignore anything that's blocked or not health/ammo/special
  1037.       if ((article.classname == "item_cells") ||
  1038.          (article.classname == "item_rockets") ||
  1039.          (article.classname == "item_shells") ||
  1040.          (article.classname == "item_spikes") ||
  1041.          (article.classname == "item_health") ||
  1042.          (article.classname == "item_artifact_super_damage") ||
  1043.          (article.classname == "item_artifact_invunerability") ||
  1044.          (article.classname == "item_artifact_invisibility"))
  1045.       {
  1046. /*        if (best_article != world)
  1047.         {
  1048. */
  1049.           if ((self.health < 50) &&
  1050.              (article.classname == "item_health"))
  1051.           {
  1052.             best_article = article;
  1053.             best_dist = o_range;
  1054.           }
  1055.           else
  1056.           if ((self.health < 50) &&
  1057.              (best_article.classname != "item_health"))
  1058.           {
  1059.             // need health, haven't found any yet
  1060.             // so this will do (if its closer than best)
  1061.             // unless we have max ammo (if its ammo)
  1062.             if ((o_range < best_dist) &&
  1063.                 (cbot_check_max_ammo(article)))
  1064.             {
  1065.               best_article = article;
  1066.               best_dist = o_range;
  1067.             }
  1068.           }  //ignore article if we have and need health
  1069.           else
  1070.           if (article.classname == "item_health")
  1071.           {
  1072.             if (self.health < self.max_health)
  1073.             {
  1074.               best_article = article;
  1075.               best_dist = o_range;
  1076.             }
  1077.           }
  1078.           else
  1079.           {
  1080.             if ((article.classname == "item_artifact_super_damage") ||
  1081.                (article.classname == "item_artifact_invunerability") ||
  1082.                (article.classname == "item_artifact_invisibility"))
  1083.             {
  1084.               //health is ok - this is a special (don't care about
  1085.               //distance either)
  1086.               best_article = article;
  1087.               best_dist = o_range;
  1088.             }
  1089.             else
  1090.             if (((best_article.classname != "item_artifact_super_damage") &&
  1091.                (best_article.classname != "item_artifact_invunerability") &&
  1092.                (best_article.classname != "item_artifact_invisibility"))  &&
  1093.                (o_range < best_dist) &&
  1094.                (cbot_check_max_ammo(article)))
  1095.             {
  1096.                 // no health prob., it's not a special. we ignore 
  1097.                 // so it is ammo - grab it.
  1098.                 best_article = article;
  1099.                 best_dist = o_range;
  1100.             }
  1101.           }  
  1102. /*        }
  1103.         else
  1104.         {
  1105.           // currently world
  1106.           best_article = article;
  1107.           best_dist = o_range;
  1108.         }
  1109. */
  1110.       }
  1111.     }
  1112.     article = article.chain; // go to next in list
  1113.   } // end while
  1114.  
  1115.   return (best_article);
  1116. };
  1117.  
  1118.  
  1119.  
  1120. void() cbot_ai_stand =
  1121. {
  1122.   local float o_range;
  1123.   local entity article;
  1124.   local float best_dist;
  1125.   local entity best_article;
  1126.   local vector camp_origin;
  1127.   local float cbot_mode;
  1128.  
  1129.  
  1130. CheckPowerups();
  1131.         CheckRules ();
  1132.         WaterMove ();
  1133.  
  1134. /* MARK for deletion - need to be able to fire while in air.
  1135.   now handled by having "JUMP" mode
  1136. if (self.botmode == JUMP)
  1137. {
  1138.   // don't do a think while in the air and not projected
  1139.   // to do any swimming.  Needed so we don't miscalculate
  1140.   // waypoint searches while in the air and heading into
  1141.   // a teleport.
  1142.   return;
  1143. }
  1144. */
  1145.  
  1146. //if in jump mode and on ground then go back to searching
  1147. if ((self.botmode == JUMP) &&
  1148.     (self.flags & FL_ONGROUND))
  1149.   self.botmode = SEARCH;
  1150.  
  1151.  
  1152. /* MARK for deletion
  1153. //Deal with Quad Damage
  1154. if (self.items & IT_QUAD)
  1155. {
  1156.   if (self.super_damage_finished < time)
  1157.   {       // just stopped
  1158.     self.items = self.items - IT_QUAD;
  1159.     self.super_damage_finished = 0;
  1160.     self.super_time = 0;
  1161.   }
  1162.   if (self.super_damage_finished > time)
  1163.     self.effects = self.effects | EF_DIMLIGHT;
  1164.   else
  1165.     self.effects = self.effects - (self.effects & EF_DIMLIGHT);
  1166. }
  1167. */
  1168.  
  1169. if (cbot_incoming_missile())
  1170.   cbot_slide(8);
  1171.  
  1172.     // Check to see if we want to attack anyone nearby
  1173. //    cbot_mode = cbot_determine_mode (world, world); 
  1174. //    if (cbot_mode == ATTACK)
  1175. //    {
  1176.       article = cbot_find_nearest_enemy();
  1177.       if (article)
  1178.       {
  1179.         cbot_mode = cbot_determine_mode (article, world); 
  1180.         // only change if it is specifically ATTACK mode - withdraw
  1181.         // or OTFATTACK can't be delt with here
  1182.         if (cbot_mode == ATTACK)
  1183.         {
  1184.           self.botmode = ATTACK;
  1185.           self.enemy = article;
  1186.         }
  1187.       }
  1188. //    }
  1189.  
  1190.  
  1191.  
  1192. if (self.botmode == WITHDRAW)
  1193.   self.enemy = world;
  1194.   self.botmode = SEARCH;  // ******* this is for now.  we need some logic
  1195.                           // behind a withdrawl
  1196.   self.goal_status = 0;
  1197. }
  1198.  
  1199. if (self.botmode == SEARCH)
  1200. {
  1201.   if (self.goal_status == 0)
  1202.   {
  1203.     // see if we have the capability to camp!
  1204.  
  1205.     if ((self.health >= 70) &&
  1206.         ((self.pi4) || (self.pi5) || (self.pi6) || (self.pi7)) &&
  1207.         (self.armorvalue >= 40) &&
  1208.         (random() < 0.60) &&
  1209.         (self.goalentity.button0))
  1210.     {
  1211.         // must be > 40% armour, > 70% health, r/l, g/l, lg or sng
  1212.         // and 60% chance we decide to
  1213.         self.camp_time = time + 120 + 90 * random();
  1214.         self.goal_status = 1;            // on a waypoint goal now
  1215.         self.botmode = CAMPER;
  1216.         camp_origin = self.goalentity.view_ofs;
  1217.         self.think = self.th_run;
  1218.         self.nextthink = time + 0.1;
  1219.         self.goalentity = spawn();  // spawn a goal at the camp posn
  1220.         self.goalentity.think = SUB_Remove;
  1221.         self.goalentity.nextthink = time + 140;
  1222.         setorigin (self.goalentity, camp_origin);
  1223.     }
  1224.     else
  1225.     {
  1226.  
  1227.       // find any nearby weapon or backpack and grab it
  1228.       article = findradius (self.origin, 212.0);
  1229.  
  1230.       best_dist = 9999.0; // set our best range to a large value;
  1231.       best_article = world;
  1232.  
  1233.       while (article)
  1234.       {
  1235.         o_range = vlen(article.origin - self.origin);
  1236.         // trace a line - raise target slightly for visibility on stairs, etc
  1237.         traceline (self.origin, article.origin + '0 0 10', TRUE, self);
  1238.         if (trace_fraction == 1.0)
  1239.         {
  1240.           // ignore anything that's blocked or not a weapon/backpack/armour
  1241.           if ((o_range <= best_dist) &&
  1242.              ((article.classname == "weapon_supershotgun") ||
  1243.              (article.classname == "weapon_nailgun") ||
  1244.              (article.classname == "weapon_supernailgun") ||
  1245.              (article.classname == "weapon_grenadelauncher") ||
  1246.              (article.classname == "weapon_rocketlauncher") ||
  1247.              (article.classname == "weapon_lightning") ||
  1248.              (article.classname == "backpack") ||
  1249.              (article.classname == "item_armor1") ||
  1250.              (article.classname == "item_armor2") ||
  1251.             (article.classname == "item_armorInv")))
  1252.           {
  1253.             if (best_article != world)
  1254.             {
  1255.               if (best_article.classname == "backpack")
  1256.               {
  1257.                 //this backpack or weapon is closer, so grab it!
  1258.                 best_article = article;
  1259.                 best_dist = o_range;
  1260.               }
  1261.               else
  1262.               {
  1263.                 // be more choosie - we already have a best weapon
  1264.                 // so only another weapon will do
  1265.                 if (article.classname != "backpack")
  1266.                 {
  1267.                   //it's a weapon so get it
  1268.                   best_article = article;
  1269.                   best_dist = o_range;
  1270.                 }
  1271.               }
  1272.             }
  1273.             else
  1274.             {
  1275.               // we have no best, so make this the best so far
  1276.               best_article = article;
  1277.               best_dist = o_range;
  1278.             }
  1279.           }
  1280.         }
  1281.         article = article.chain; // go to next in list
  1282.       } // end while
  1283.  
  1284.       // if we find a sub-goal, go to it.
  1285.       if (best_article != world)
  1286.       {
  1287.         // we have a local target, so make this a sub-goal
  1288.         cbot_create_subgoal(best_article);
  1289.         self.think = self.th_run;
  1290.         self.nextthink = time + 0.1;
  1291.       }
  1292.       else
  1293.       {
  1294.         // try to get a new waypoint - general or item
  1295.         best_article = cbot_get_waypoint(0,100,0);
  1296.         if (best_article != world)
  1297.         {
  1298.           // got a waypoint to go to, so do it!
  1299.           self.goalentity = best_article;  // article is the goal
  1300.           self.goal_status = 1;            // on a waypoint goal now
  1301.           self.think = self.th_run;
  1302.           self.nextthink = time + 0.1;
  1303.         }
  1304.         else
  1305.         {
  1306.           // oops!  can't find a waypoint!
  1307.           // try a search strategy
  1308. //bprint ("stuck - try search\n");
  1309.           best_article = cbot_get_waypoint(0,100,1);
  1310.           if (best_article != world)
  1311.           {
  1312.             // find the preceeding waypoint to closest waypoint
  1313.             best_article = find (world, target, best_article.targetname);
  1314.             if (best_article != world)
  1315.             {
  1316.               self.goalentity = best_article;
  1317.               self.goal_status = 1;
  1318.               self.think = self.th_run;
  1319.               self.nextthink = time + 0.1;
  1320.               return;
  1321.             }
  1322. //            else
  1323. //bprint ("no previous waypoint\n");
  1324.           }
  1325. //bprint ("no waypoint found - idling\n");
  1326.           self.angles_y = 360 * random();
  1327.           cbot_jump();
  1328.         }
  1329.       }
  1330.     }
  1331.   }  // end if self.goal_status = 0
  1332.   else
  1333.   if (self.goal_status = 2)
  1334.   {
  1335.     //off-route on a sub-goal so backtrack to original posn
  1336.     self.goal_status = 4;  //on-route backtracking
  1337.     self.goalentity = self.origin_save;
  1338.     self.think = self.th_run;
  1339.     self.nextthink = time + 0.1;
  1340.   }
  1341.  
  1342.   self.watchdog_time = time + 1.2;  // try again in 1.2 seconds
  1343. }
  1344. else if (self.botmode == CAMPER)
  1345. {
  1346.   // we must have reached the camp wpt in th_stand - so now
  1347.   // we just wait it out!
  1348.  
  1349.   // check to see if we want to attack anyone nearby
  1350. //  if (cbot_determine_mode (world, world) == ATTACK)
  1351. //  {
  1352.     self.enemy = cbot_find_nearest_enemy();
  1353.     if (self.enemy != world)
  1354.     {
  1355.       self.botmode = cbot_determine_mode (self.enemy, world);
  1356.       return;
  1357.     }
  1358. //  }
  1359.  
  1360.  
  1361.   if (self.camp_time < time)
  1362.     self.botmode = SEARCH;
  1363.  
  1364.   // Face cbot towards the specified angle
  1365.   self.ideal_yaw = self.goalentity.angles_y;
  1366.   ChangeYaw();
  1367.  
  1368. }
  1369. else if (self.botmode == ATTACK)
  1370. {
  1371.   // attack until the enemy is dead
  1372.   cbot_attack_initial();
  1373.   if (self.enemy.health <= 0)
  1374.   {
  1375. //bprint("Enemy is dead!\n");
  1376.     //enemy is dead, so continue
  1377.     self.enemy = world;
  1378.     self.botmode = SEARCH;
  1379.     self.goal_status = 0;
  1380.     self.angles_x = 0;  // make sure we reset the pitch on loss of target!
  1381.                         // should really be ChangePitch here
  1382.   }
  1383.   if (self.weapon == IT_NONE)
  1384.   {
  1385. //bprint ("out of ammo\n");
  1386.     // must have run out of ammo
  1387.     self.enemy = world;
  1388.     self.botmode = WITHDRAW;
  1389.     self.goal_status = 0;
  1390.     self.angles_x = 0;  // make sure we reset the pitch on loss of target!
  1391.                         // should really be ChangePitch here
  1392.   }
  1393. }
  1394. else if (self.botmode == OTFATTACK)
  1395. {
  1396.   // only make one attack and then continue search
  1397.   cbot_attack_initial();
  1398.   self.enemy = world;
  1399.   self.botmode = SEARCH;
  1400. }
  1401.  
  1402. };
  1403.  
  1404.  
  1405.  
  1406.  
  1407.  
  1408.  
  1409. void(float dist) cbot_ai_walk =
  1410. {
  1411.   local entity trywpt;
  1412.   local float o_range;
  1413.   local float h_range;
  1414.   local vector h1, h2;
  1415.   local float cont;
  1416.   local vector norm_vel;
  1417.   local entity other_item;
  1418.   local float cbot_mode;
  1419.   local entity article;
  1420.  
  1421. local string sf;
  1422.  
  1423.  
  1424. if (cbot_incoming_missile())
  1425.   cbot_slide(8);
  1426.  
  1427. CheckPowerups();
  1428.  
  1429. if (self.botmode == SEARCH)
  1430. {
  1431. //  cont = pointcontents(self.origin);
  1432.  
  1433.   if (self.goalentity.swim_flag)
  1434.   {
  1435.     // if we hit a waypoint that says swim, start testing
  1436.     // for water (probably jumping into it, in which case we
  1437.     // don't want to change our velocity until we are in the water)
  1438.  
  1439.  
  1440.     if ((self.watertype == CONTENT_WATER) ||
  1441.         (self.watertype == CONTENT_SLIME))
  1442.     {
  1443.       // tread water if we are swimming *in* water/slime
  1444.       self.velocity_z = self.goalentity.origin_z - self.origin_z;
  1445.       if (self.velocity_z > 0)
  1446.         self.velocity_z = self.velocity_z * 3.3; //give "up" some kick
  1447.       self.velocity_y = self.goalentity.origin_y - self.origin_y;
  1448.       self.velocity_x = self.goalentity.origin_x - self.origin_x;
  1449.       norm_vel = normalize (self.velocity) * 70;
  1450.       norm_vel_z = 0;
  1451.       self.velocity = self.velocity + norm_vel;
  1452.     }
  1453.   }
  1454.  
  1455.         CheckRules ();
  1456.         WaterMove ();
  1457.  
  1458. /*  if (cont == CONTENT_LAVA)
  1459.   {
  1460.       // do damage from lava
  1461.       T_Damage (self, world, world, 100);
  1462.   }
  1463. */
  1464.  
  1465.   if (self.goal_status == 1)
  1466.   {
  1467.     if (self.goalentity.items == 0)
  1468.     {
  1469.       // if on a general path, check for other prioritised paths 
  1470.       // first - take them if they are important
  1471.       trywpt = cbot_get_waypoint(1,0,0);
  1472.       
  1473.       if (trywpt != world)
  1474.       {
  1475. //bprint ("got prioritised goal\n");
  1476.         // got an item waypoint to go to, so do it!
  1477.         self.goalentity = trywpt;        // trywpt is the goal
  1478.         self.goal_status = 1;            // still on a waypoint goal
  1479.         self.think = self.th_run;
  1480.         self.nextthink = time + 0.1;
  1481.         return;  // exit from current course
  1482.       }
  1483.     }
  1484.     else
  1485.     {
  1486.       // we are on a non-general waypoint, but see what else
  1487.       // there is that is *better* than what we are going for.
  1488.       trywpt = cbot_get_waypoint(1,0,0);
  1489.  
  1490.       if (trywpt != world)
  1491.         if (trywpt.items > self.goalentity.items)
  1492.         {
  1493. //bprint ("swapping goals\n");
  1494.           // trywpt is a better item so get it
  1495.           self.goalentity = trywpt;        // trywpt is the goal
  1496.           self.goal_status = 1;            // still on a waypoint goal
  1497.           self.think = self.th_run;
  1498.           self.nextthink = time + 0.1;
  1499.           return;  // exit from current course
  1500.         }
  1501.     }
  1502.  
  1503.     // Now check for other items of interest (health, special, ammo)
  1504.     // if the other_item_time watch timer has gone off
  1505.     if (self.other_item_time < time)
  1506.     {
  1507.       other_item = find_otf_items();  //look for other items (health etc)
  1508.       if (other_item != world)
  1509.       {
  1510.         cbot_create_subgoal(other_item);    // we found an otf-item so get it
  1511.         self.other_item_time = time + 0.3;  // try again in 0.3 seconds
  1512.         self.think = self.th_run;
  1513.         self.nextthink = time + 0.1;
  1514.         return;
  1515.       }
  1516.     }
  1517.  
  1518.     // on-route to target
  1519.     o_range = vlen (self.origin - self.goalentity.origin);
  1520.     h1 = self.origin; h1_z = 0;
  1521.     h2 = self.goalentity.origin; h2_z = 0;
  1522.     h_range = vlen(h1 - h2);
  1523.  
  1524.     if (o_range < 70)  // check full range
  1525.     if (h_range < 20)  // check horizontal range
  1526.     {
  1527.       if (self.goalentity.jump_flag)
  1528.       {
  1529.         self.goalentity.jump_flag = self.goalentity.jump_flag - 1;
  1530.         // we have to activate a button by jumping
  1531.         if ( (self.flags & FL_ONGROUND) )
  1532.         {
  1533.           self.botmode = JUMP;
  1534.           self.flags = self.flags - FL_ONGROUND;
  1535.           self.velocity_z = 50;
  1536.         }
  1537.         return;
  1538.       }
  1539.  
  1540.       if (self.goalentity.speed > 0)
  1541.       {
  1542.         // jump if speed is greater than 0 on waypoint
  1543.         // set XY even if not on ground, so the jump will clear lips
  1544.         self.velocity_x = self.goalentity.movedir_x * self.goalentity.speed;
  1545.         self.velocity_y = self.goalentity.movedir_y * self.goalentity.speed;
  1546.         
  1547.         if ( (self.flags & FL_ONGROUND) )
  1548.         {
  1549.           self.flags = self.flags - FL_ONGROUND;
  1550.           self.velocity_z = self.goalentity.height;
  1551.         }
  1552.  
  1553.         self.nextthink = time + 0.2;  //allow some time
  1554.  
  1555.         sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
  1556.       }
  1557.  
  1558.       // got to the target so stop if appropriate.
  1559.       if ((self.goal_status == 1) &&
  1560.           (self.goalentity.target != "-"))
  1561.       {
  1562.         // still on waypoint route - find next waypoint.
  1563.         // first, see if we have a choice - randomize it
  1564.         if (self.goalentity.noise4)
  1565.           if (random () > 0.4)
  1566.             self.goalentity = find (world, targetname, self.goalentity.noise4);
  1567.           else
  1568.             self.goalentity = find (world, targetname, self.goalentity.target);
  1569.         else
  1570.           self.goalentity = find (world, targetname, self.goalentity.target);
  1571.       }
  1572.       else
  1573.       {
  1574.         // end of waypoint route
  1575.         self.goal_status = 0;
  1576.         self.think = self.th_stand;
  1577.       }
  1578.     }
  1579.   }
  1580.   else
  1581.   if ((self.goal_status == 3) ||
  1582.       (self.goal_status == 4))
  1583.   {
  1584.     // moving via sub-routes
  1585.     if (self.goal_status == 3)
  1586.     {
  1587.       o_range = vlen (self.origin - self.goalentity.origin);
  1588.       h1 = self.origin; h1_z = 0;
  1589.       h2 = self.goalentity.origin; h2_z = 0;
  1590.       h_range = vlen(h1 - h2);
  1591.  
  1592.       if (o_range < 70)  // check full range
  1593.       if (h_range < 20)  // check horizontal range
  1594.       {
  1595.         self.goal_status = 2;
  1596.         self.think = self.th_stand;
  1597.         remove (self.goalentity);  // remove the target position
  1598.         return;
  1599.       }
  1600.     }
  1601.     else
  1602.     if (self.goal_status == 4)
  1603.     {
  1604.       o_range = vlen (self.origin - self.origin_save.origin);
  1605.       h1 = self.origin; h1_z = 0;
  1606.       h2 = self.origin_save.origin; h2_z = 0;
  1607.       h_range = vlen(h1 - h2);
  1608.  
  1609.       if (o_range < 70)  // check full range
  1610.       if (h_range < 20)  // check horizontal range
  1611.       {
  1612.         // finished on a backtrack route
  1613.         if (self.oldgoalname != "none")
  1614.         {
  1615.           // find our original goal target (before we got interrupted
  1616.           // to do the sub-goal)
  1617. //bprint ("looking for original target ");
  1618. //bprint (self.oldgoalname);
  1619. //bprint("\n");
  1620.           self.goalentity = find (world, targetname, self.oldgoalname);
  1621.           self.goal_status = 1;
  1622.         }
  1623.         else
  1624.         {
  1625.           // no original target so stop and think
  1626.           self.goal_status = 0;
  1627.           self.think = self.th_stand;
  1628.         }
  1629.         remove (self.origin_save);  //remove the saved position
  1630.         self.nextthink = time + 0.1;
  1631.         return;
  1632.       }
  1633.     }
  1634.   }
  1635. }
  1636. else if (self.botmode == CAMPER)
  1637. {
  1638.   // move towards the camp goal
  1639.   // no need to check for waypoint types as once we get
  1640.   // there, th_stand handles everything (and clears CAMPER mode).
  1641.  
  1642.       o_range = vlen (self.origin - self.goalentity.origin);
  1643.       h1 = self.origin; h1_z = 0;
  1644.       h2 = self.goalentity.origin; h2_z = 0;
  1645.       h_range = vlen(h1 - h2);
  1646.  
  1647.       if (o_range < 70)  // check full range
  1648.       if (h_range < 20)  // check horizontal range
  1649.       {
  1650.         self.goal_status = 0;  // finished - this can be anything
  1651.         self.think = self.th_stand;
  1652.         remove (self.goalentity);  // remove the target position
  1653.         return;
  1654.       }
  1655. }
  1656. else if ((self.botmode == ATTACK) ||
  1657.          (self.botmode == OTFATTACK) ||
  1658.          (self.botmode == WITHDRAW) ||
  1659.          (self.botmode == JUMP))
  1660. {
  1661.   // we have to swap modes, so stop running and start thinking
  1662.   self.think = self.th_stand;
  1663.   self.nextthink = time + 0.1;
  1664. }
  1665.   // *** General stuff that we have to do every time we move ***
  1666.  
  1667.   //Turn to look at the target
  1668.   self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
  1669.   ChangeYaw();
  1670.  
  1671.   movetogoal (dist);
  1672.  
  1673.   if (self.watchdog_time < time)
  1674.   {
  1675.     cbot_watchdog();
  1676.     self.watchdog_time = time + 0.2;  // try again in 0.2 seconds
  1677.  
  1678.     // also check to see if we want to attack anyone nearby
  1679. //    cbot_mode = cbot_determine_mode (world, world); 
  1680. //    if (cbot_mode == ATTACK)
  1681. //    {
  1682.       article = cbot_find_nearest_enemy();
  1683.       if (article)
  1684.       {
  1685.         cbot_mode = cbot_determine_mode (article, world); 
  1686.         // only change if it is specifically ATTACK mode - withdraw
  1687.         // or OTFATTACK can't be delt with here
  1688.         if (cbot_mode == ATTACK)
  1689.         {
  1690.           self.botmode = ATTACK;
  1691.           self.enemy = article;
  1692.         }
  1693.       }
  1694.  
  1695.   }
  1696.  
  1697.   if (self.prioritise_time < time)
  1698.   {
  1699.     cbot_prioritise();
  1700.     self.prioritise_time = time + 1;  // do this slowly as we are not
  1701.                                       // in attack/defence mode
  1702.   }
  1703.  
  1704.   // only set a new time if it hasn't been done already (eg: in jump)
  1705.   if (self.nextthink < time)
  1706.     self.nextthink = time + 0.1;  // add time
  1707.  
  1708.  
  1709. };
  1710.  
  1711. void(float dist) cbot_ai_run =
  1712. {
  1713.   cbot_ai_walk(dist);
  1714. };
  1715.  
  1716.  
  1717.